package com.lottery.system.service.mq;

import cn.hutool.core.date.DateUtil;
import com.lottery.system.common.exception.ServiceException;
import com.lottery.system.common.utils.JacksonUtil;
import com.lottery.system.common.utils.MailUtil;
import com.lottery.system.common.utils.SMSUtil;
import com.lottery.system.controller.param.DrawPrizeParam;
import com.lottery.system.dao.dataobject.ActivityPrizeDO;
import com.lottery.system.dao.dataobject.WinningRecordDO;
import com.lottery.system.dao.mapper.ActivityPrizeMapper;
import com.lottery.system.dao.mapper.WinningRecordMapper;
import com.lottery.system.service.DrawPrizeService;
import com.lottery.system.service.activitystatus.ActivityStatusManager;
import com.lottery.system.service.dto.ConvertActivityDTO;
import com.lottery.system.service.enums.ActivityPrizeStatusEnum;
import com.lottery.system.service.enums.ActivityPrizeTiersEnum;
import com.lottery.system.service.enums.ActivityStatusEnum;
import com.lottery.system.service.enums.ActivityUserStatusEnum;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import static com.lottery.system.common.config.DirectRabbitConfig.QUEUE_NAME;

/**
 * @author: xiaoxie
 * create: 2024-09-11 08:37
 * @BelongsProject: lottery-system
 * @BelongsPackage: com.lottery.system.service.mq
 * description: 信息队列接收者
 */
@Component
@RabbitListener(queues = QUEUE_NAME)
public class MqReceiver{
    private final static Logger logger = LoggerFactory.getLogger(MqReceiver.class);
    @Autowired
    private DrawPrizeService drawPrizeService;
    @Autowired
    private ActivityStatusManager activityStatusManager;
    @Autowired
    private ThreadPoolTaskExecutor asyncServiceExecutor;
    @Autowired
    private MailUtil mailUtil;
    @Autowired
    private SMSUtil smsUtil;
    @Autowired
    private ActivityPrizeMapper activityPrizeMapper;
    @Autowired
    private WinningRecordMapper winningRecordMapper;
    @RabbitHandler
    public void process(Map<String, String> message) {
        // 成功接收到队列中的消息
        logger.info("MQ成功接收到消息，message:{}",
               JacksonUtil.writeValueAsString(message));
        String paramString = message.get("messageData");
        DrawPrizeParam param = JacksonUtil.readValue(paramString, DrawPrizeParam.class);
        // 处理抽奖的流程
        try{

            //验证活动是否有效
            if(!drawPrizeService.checkDrawPrizeParam(param)) {
                return;
            }
            //状态扭转
            convertStatus(param);

            //保存中奖信息
            List<WinningRecordDO> winningRecordDOList = drawPrizeService.savaActivityWinningInfo(param);
            //发送中奖通知短信,邮箱(并发)
            asyncSendInform(winningRecordDOList);
        }catch(ServiceException e){
            logger.error("处理MQ信息出现异常:{},{}",e.getCode(),e.getMessage(),e);
            //回滚
            rollBack(param);
            //重新抛异常,让MQ重发信息.
            throw e;
        }catch (Exception e) {
            logger.error("处理MQ信息出现异常: ",e);
            //回滚
            rollBack(param);
            //重新抛异常,让MQ重发信息.
            throw e;
        }


        //处理异常,捕获异常(为了保证事务的一致性),抛出异常,让MQ重发信息.
    }
    /**
     * @description: 出现异常,进行回滚操作
     * @author: xiaoxie
     * @date: 2024/9/15 20:57
     * @param: [param]
     * @return: void
     **/
    private void rollBack(DrawPrizeParam param) {
        //1.判断扭转状态是否需要回滚
        if(!statusNeedRollBack(param)) {
            //如果不需要就返回
            return;
        }
        //如果需要就回滚
        statusRollBack(param);
        //2.判断保存中奖记录是否需要回滚
        if(!winnerRecordsNeedRollBack(param)) {
            //如果不需要就返回
            return;
        }
        //如果需要就回滚
        winnerRecordsRollBack(param);
    }

    private void winnerRecordsRollBack(DrawPrizeParam param) {
        drawPrizeService.rollBackWinnerRecords(param.getActivityId(), param.getPrizeId());
    }

    /**
     * @description: 中奖记录是否需要回滚
     * @author: xiaoxie
     * @date: 2024/9/15 21:31
     * @param: [param]
     * @return: boolean
     **/
    private boolean winnerRecordsNeedRollBack(DrawPrizeParam param) {
        //判断中奖记录表是否有数据即可,
        //有就需要,没有就不需要
        int count = winningRecordMapper.countActivityAndPrizeId(param.getActivityId()
                                                               ,param.getPrizeId());
        return count > 0;
    }
    /**
     * @description: 扭转状态回滚
     * @author: xiaoxie
     * @date: 2024/9/15 21:10
     * @param: [param]
     * @return: void
     **/
    private void statusRollBack(DrawPrizeParam param) {
        ConvertActivityDTO convertActivityDTO = new ConvertActivityDTO();
        convertActivityDTO.setActivityId(param.getActivityId());
        convertActivityDTO.setActivityStatusTarget(ActivityStatusEnum.RUNNING);
        convertActivityDTO.setPrizeId(param.getPrizeId());
        convertActivityDTO.setActivityPrizeStatusTarget(ActivityPrizeStatusEnum.INIT);
        convertActivityDTO.setUserIds(
                param.getWinnerList()
                        .stream()
                        .map(DrawPrizeParam.Winner :: getUserId)
                        .toList()
        );
        convertActivityDTO.setActivityUserStatusTarget(ActivityUserStatusEnum.INIT);
        activityStatusManager.rollBackHandleEvent(convertActivityDTO);

    }

    /**
     * @description: 扭转状态是否需要回滚
     * @author: xiaoxie
     * @date: 2024/9/15 21:10
     * @param: [param]
     * @return: boolean
     **/
    private boolean statusNeedRollBack(DrawPrizeParam param) {
        //由于在扭转状态的方法中,设置了事务回滚操作,保证一致性的原因
        //所以只需要判断活动/人员 是否已经扭转过了,活动(需要判断奖品是否全部抽完,所以不可以作为判断的依据)
        //就可以判断整个方法是否需要回滚.
        ActivityPrizeDO activityPrizeDO = activityPrizeMapper.selectActivityPrizeByIdAndActivityId(param.getActivityId()
                                                              ,param.getActivityId());
        if(null == activityPrizeDO){
            logger.warn("奖品活动表中数据不存在!");
            return false;
        }
        return activityPrizeDO.getStatus().equalsIgnoreCase(ActivityPrizeStatusEnum.COMPLETED.name());
    }

    /**
     * @description: 异步发送中奖通知
     * @author: xiaoxie
     * @date: 2024/9/14 20:25
     * @param: [winningRecordDOList]
     * @return: void
     **/
    private void asyncSendInform(List<WinningRecordDO> winningRecordDOList) {
        asyncServiceExecutor.execute(()-> sendMail(winningRecordDOList));
        asyncServiceExecutor.execute(()-> sendMessage(winningRecordDOList));
    }

    /**
     * @description: 发送短信
     * @author: xiaoxie
     * @date: 2024/9/14 20:30
     * @param: [winningRecordDOList]
     * @return: void
     **/
    private void sendMessage(List<WinningRecordDO> winningRecordDOList) {
        if(winningRecordDOList == null || winningRecordDOList.isEmpty()){
            logger.warn("asyncSendInform sendMessage: 中奖信息为空,无法发送短信!");
            return;
        }
        for(WinningRecordDO winningRecordDO : winningRecordDOList){
            Map<String,String> map = new HashMap<>();
            map.put("name",winningRecordDO.getWinnerName());
            map.put("activityName", winningRecordDO.getActivityName());
            map.put("prizeTiers", Objects.requireNonNull(ActivityPrizeTiersEnum.forName
                    (winningRecordDO.getPrizeTier()))
                    .getMessage());
            map.put("prizeName",winningRecordDO.getPrizeName());
            map.put("winningTime", DateUtil.formatTime(winningRecordDO.getWinningTime()));
            smsUtil.sendMessage("SMS_473360114",winningRecordDO.getWinnerPhone().getValue(),JacksonUtil.writeValueAsString(map));
        }

    }

    /**
     * @description: 发送邮箱
     * @author: xiaoxie
     * @date: 2024/9/14 20:30
     * @param: [winningRecordDOList]
     * @return: void
     **/
    private void sendMail(List<WinningRecordDO> winningRecordDOList) {
        if(winningRecordDOList == null || winningRecordDOList.isEmpty()){
            logger.warn("asyncSendInform sendMessage: 中奖信息为空,无法发送邮件!");
            return;
        }
        for (WinningRecordDO winningRecordDO : winningRecordDOList) {
            mailUtil.sendSampleMail(winningRecordDO.getWinnerEmail(),"柚来抽",constructorContext(winningRecordDO));
        }
    }

    /**
     * @description: 构造邮件正文
     * @author: xiaoxie
     * @date: 2024/9/14 20:45
     * @param: [winningRecordDO]
     * @return: java.lang.String
     **/
    private String constructorContext(WinningRecordDO winningRecordDO) {
        return  "Hi，" + winningRecordDO.getWinnerName() + "。恭喜你在"
                + winningRecordDO.getActivityName() + "活动中获得"
                + Objects.requireNonNull(ActivityPrizeTiersEnum.forName(winningRecordDO.getPrizeTier())).getMessage()
                + "：" +winningRecordDO.getPrizeName() + "。获奖时间为"
                + DateUtil.formatTime(winningRecordDO.getWinningTime()) + "，请尽快领取您的奖励！";
    }


    /**
     * @description: 扭转状态
     * @author: xiaoxie
     * @date: 2024/9/11 15:08
     * @param: [param]
     * @return: void
     **/
    private void convertStatus(DrawPrizeParam param) {

        //使用策略和责任链设计模式
        ConvertActivityDTO convertActivityDTO = new ConvertActivityDTO();

        convertActivityDTO.setActivityId(param.getActivityId());
        convertActivityDTO.setActivityStatusTarget(ActivityStatusEnum.COMPLETED);

        convertActivityDTO.setPrizeId(param.getPrizeId());
        convertActivityDTO.setActivityPrizeStatusTarget(ActivityPrizeStatusEnum.COMPLETED);

        convertActivityDTO.setUserIds(
                param.getWinnerList()
                        .stream()
                        .map(DrawPrizeParam.Winner::getUserId)
                        .distinct()
                        .toList()
        );
        convertActivityDTO.setActivityUserStatusTarget(ActivityUserStatusEnum.COMPLETED);

        activityStatusManager.handleEvent(convertActivityDTO);
    }
  //  private void convertStatus(DrawPrizeParam param) {

//        // 问题：
//        // 1、活动状态扭转有依赖性，导致代码维护性差
//        // 2、状态扭转条件可能会扩展，当前写法，扩展性差，维护性差
//        // 3、代码的灵活性、扩展性、维护性极差
//        // 解决方案：设计模式（责任链设计模式、策略模式）重点;
//
        //校验活动,人员,奖品状态

        //扭转人员状态

        //扭转奖品状态


        //最后扭转活动状态,
        //前提,活动的所有奖品状态要不为有效
        //才扭转



    //}
}
